﻿using System.Collections.Generic;
using System.Linq;
using Ahresty.Entity;

namespace Ahresty.Algorithms
{
    /// <summary>
    /// 点接触の要素を操作するクラス
    /// </summary>
    internal class PointTouchHandler
    {
        #region Methods

        /// <summary>
        /// 指定の材料データから点接触要素を探し出す
        /// </summary>
        internal static PointTouch[] LookForPointTouchs(IN3DData in3dData)
        {
            List<PointTouch> result = new List<PointTouch>();

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        result.AddRange(PointTouchHandler.CheckPointTouch(in3dData, x, y, z));

            return result.ToArray();
        }

        /// <summary>
        /// 指定の材料データから点接触要素を探し出す（要素の数のみを返す）
        /// </summary>
        internal static int LookForPointTouchCount(IN3DData in3dData)
        {
            int result = 0;

            for (short z = 0; z < in3dData.MaxZ; z++)
                for (short y = 0; y < in3dData.MaxY; y++)
                    for (short x = 0; x < in3dData.MaxX; x++)
                        result += PointTouchHandler.CheckPointTouch(in3dData, x, y, z).Length;

            return result;
        }

        /// <summary>
        /// 指定の要素が点接触かどうかをチェックする
        /// </summary>
        internal static PointTouch[] CheckPointTouch(IN3DData in3dData, short x, short y, short z)
        {
            List<PointTouch> result = new List<PointTouch>();

            byte value = in3dData.Data[x, y, z];
            if (value != in3dData.MaterialID)
                return result.ToArray();

            int maxX = in3dData.MaxX;
            int maxY = in3dData.MaxY;
            int maxZ = in3dData.MaxZ;

            //X+Y+Z+ & X+Y-Z+
            if (x + 1 < maxX && z + 1 < maxZ &&
                in3dData.Data[x + 1, y, z] != value && in3dData.Data[x, y, z + 1] != value && in3dData.Data[x + 1, y, z + 1] != value)
            {
                //X+Y+Z+
                if (y + 1 < maxY && in3dData.Data[x + 1, y + 1, z + 1] == value &&
                    in3dData.Data[x, y + 1, z] != value && in3dData.Data[x + 1, y + 1, z] != value && in3dData.Data[x, y + 1, z + 1] != value)
                    result.Add(new PointTouch
                    {
                        X = x,
                        Y = y,
                        Z = z,
                        FixCandidates1 = new Unit[4] {
                            new Unit { X = x, Y = (short)(y + 1), Z = z, }, new Unit { X = (short)(x + 1), Y = (short)(y + 1), Z = z, },
                            new Unit { X = x, Y = y, Z = (short)(z + 1), }, new Unit { X = (short)(x + 1), Y = y, Z = (short)(z + 1), },
                        },
                        FixCandidates2 = new Unit[4] {
                            new Unit { X = (short)(x + 1), Y = y, Z = z, }, new Unit { X = (short)(x + 1), Y = (short)(y + 1), Z = z, },
                            new Unit { X = x, Y = y, Z = (short)(z + 1), }, new Unit { X = x, Y = (short)(y + 1), Z = (short)(z + 1), },
                        },
                        FixCandidates3 = new Unit[4] {
                            new Unit { X = x, Y = (short)(y + 1), Z = z, }, new Unit { X = x, Y = (short)(y + 1), Z = (short)(z + 1), },
                            new Unit { X = (short)(x + 1), Y = y, Z = z, }, new Unit { X = (short)(x + 1), Y = y, Z = (short)(z + 1), },
                        },
                    });

                //X+Y-Z+
                if (y - 1 >= 0 && in3dData.Data[x + 1, y - 1, z + 1] == value &&
                    in3dData.Data[x, y - 1, z] != value && in3dData.Data[x + 1, y - 1, z] != value && in3dData.Data[x, y - 1, z + 1] != value)
                    result.Add(new PointTouch
                    {
                        X = x,
                        Y = y,
                        Z = z,
                        FixCandidates1 = new Unit[4] {
                            new Unit { X = x, Y = y, Z = (short)(z + 1), }, new Unit { X = (short)(x + 1), Y = y, Z = (short)(z + 1), },
                            new Unit { X = x, Y = (short)(y - 1), Z = z, }, new Unit { X = (short)(x + 1), Y = (short)(y - 1), Z = z, },
                        },
                        FixCandidates2 = new Unit[4] {
                            new Unit { X = x, Y = (short)(y - 1), Z = (short)(z + 1), }, new Unit { X = x, Y = y, Z = (short)(z + 1), },
                            new Unit { X = (short)(x + 1), Y = (short)(y - 1), Z = z, }, new Unit { X = (short)(x + 1), Y = y, Z = z, },
                        },
                        FixCandidates3 = new Unit[4] {
                            new Unit { X = x, Y = (short)(y - 1), Z = z, }, new Unit { X = x, Y = (short)(y - 1), Z = (short)(z + 1), },
                            new Unit { X = (short)(x + 1), Y = y, Z = z, }, new Unit { X = (short)(x + 1), Y = y, Z = (short)(z + 1), },
                        },
                    });
            }

            //X-Y+Z+ && X-Y-Z+
            if (x - 1 >= 0 && z + 1 < maxZ &&
                in3dData.Data[x - 1, y, z] != value && in3dData.Data[x, y, z + 1] != value && in3dData.Data[x - 1, y, z + 1] != value)
            {
                //X-Y+Z+
                if (y + 1 < maxY && in3dData.Data[x - 1, y + 1, z + 1] == value &&
                    in3dData.Data[x, y + 1, z] != value && in3dData.Data[x - 1, y + 1, z] != value && in3dData.Data[x, y + 1, z + 1] != value)
                    result.Add(new PointTouch
                    {
                        X = x,
                        Y = y,
                        Z = z,
                        FixCandidates1 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = y, Z = (short)(z + 1), }, new Unit { X = x, Y = y, Z = (short)(z + 1), },
                            new Unit { X = (short)(x - 1), Y = (short)(y + 1), Z = z, }, new Unit { X = x, Y = (short)(y + 1), Z = z, },
                        },
                        FixCandidates2 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = y, Z = z, }, new Unit { X = (short)(x - 1), Y = (short)(y + 1), Z = z, },
                            new Unit { X = x, Y = y, Z = (short)(z + 1), }, new Unit { X = x, Y = (short)(y + 1), Z = (short)(z + 1), },
                        },
                        FixCandidates3 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = y, Z = z, }, new Unit { X = (short)(x - 1), Y = y, Z = (short)(z + 1), },
                            new Unit { X = x, Y = (short)(y + 1), Z = z, }, new Unit { X = x, Y = (short)(y + 1), Z = (short)(z + 1), },
                        },
                    });

                //X-Y-Z+
                if (y - 1 >= 0 && in3dData.Data[x - 1, y - 1, z + 1] == value &&
                    in3dData.Data[x, y - 1, z] != value && in3dData.Data[x - 1, y - 1, z] != value && in3dData.Data[x, y - 1, z + 1] != value)
                    result.Add(new PointTouch
                    {
                        X = x,
                        Y = y,
                        Z = z,
                        FixCandidates1 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = (short)(y - 1), Z = z, }, new Unit { X = x, Y = (short)(y - 1), Z = z, },
                            new Unit { X = (short)(x - 1), Y = y, Z = (short)(z + 1), }, new Unit { X = x, Y = y, Z = (short)(z + 1), },
                        },
                        FixCandidates2 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = (short)(y - 1), Z = z, }, new Unit { X = (short)(x - 1), Y = y, Z = z, },
                            new Unit { X = x, Y = (short)(y - 1), Z = (short)(z + 1), }, new Unit { X = x, Y = y, Z = (short)(z + 1), },
                        },
                        FixCandidates3 = new Unit[4] {
                            new Unit { X = (short)(x - 1), Y = y, Z = z, }, new Unit { X = (short)(x - 1), Y = y, Z = (short)(z + 1), },
                            new Unit { X = x, Y = (short)(y - 1), Z = z, }, new Unit { X = x, Y = (short)(y - 1), Z = (short)(z + 1), },
                        },
                    });
            }

            return result.ToArray();
        }

        /// <summary>
        /// 指定の点接触を修正する
        /// </summary>
        /// <param name="pt">点接触</param>
        /// <param name="units">関連要素の情報</param>
        /// <param name="in3dData">修正対象となる材料データ</param>
        internal static void FixPointTouch(PointTouch pt, Unit[] units, ref IN3DData in3dData, byte fixMaterialID)
        {
            double occSum1 = 0;
            foreach (Unit u in pt.FixCandidates1)
                occSum1 += units.First(unit => unit.X == u.X && unit.Y == u.Y && unit.Z == u.Z).OccupiedRatio;

            double occSum2 = 0;
            foreach (Unit u in pt.FixCandidates2)
                occSum2 += units.First(unit => unit.X == u.X && unit.Y == u.Y && unit.Z == u.Z).OccupiedRatio;

            double occSum3 = 0;
            foreach (Unit u in pt.FixCandidates3)
                occSum3 += units.First(unit => unit.X == u.X && unit.Y == u.Y && unit.Z == u.Z).OccupiedRatio;

            if (occSum1 >= occSum2 && occSum1 >= occSum3)//補充要素を要素Ⅰに決定
            {
                in3dData.Data[pt.FixCandidates1[0].X, pt.FixCandidates1[0].Y, pt.FixCandidates1[0].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates1[1].X, pt.FixCandidates1[1].Y, pt.FixCandidates1[1].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates1[2].X, pt.FixCandidates1[2].Y, pt.FixCandidates1[2].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates1[3].X, pt.FixCandidates1[3].Y, pt.FixCandidates1[3].Z] = fixMaterialID;
            }
            else if (occSum2 >= occSum3 && occSum2 >= occSum1)//補充要素を要素Ⅱに決定
            {
                in3dData.Data[pt.FixCandidates2[0].X, pt.FixCandidates2[0].Y, pt.FixCandidates2[0].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates2[1].X, pt.FixCandidates2[1].Y, pt.FixCandidates2[1].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates2[2].X, pt.FixCandidates2[2].Y, pt.FixCandidates2[2].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates2[3].X, pt.FixCandidates2[3].Y, pt.FixCandidates2[3].Z] = fixMaterialID;
            }
            else//補充要素を要素Ⅲに決定
            {
                in3dData.Data[pt.FixCandidates3[0].X, pt.FixCandidates3[0].Y, pt.FixCandidates3[0].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates3[1].X, pt.FixCandidates3[1].Y, pt.FixCandidates3[1].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates3[2].X, pt.FixCandidates3[2].Y, pt.FixCandidates3[2].Z] = fixMaterialID;
                in3dData.Data[pt.FixCandidates3[3].X, pt.FixCandidates3[3].Y, pt.FixCandidates3[3].Z] = fixMaterialID;
            }
        }

        #endregion
    }

    /// <summary>
    /// 点接触要素を表すクラス
    /// </summary>
    internal class PointTouch : Unit
    {
        #region Properties

        /// <summary>
        /// 補充要素集合Ⅰ
        /// </summary>
        internal Unit[] FixCandidates1 { get; set; }

        /// <summary>
        /// 補充要素集合Ⅱ
        /// </summary>
        internal Unit[] FixCandidates2 { get; set; }

        /// <summary>
        /// 補充要素集合Ⅲ
        /// </summary>
        internal Unit[] FixCandidates3 { get; set; }

        #endregion
    }
}